import asyncio


class MM:
  def __init__(self, logger, pair, lot, spread_amount, spread_band):
    self._logger   = logger
    self._pair     = pair
    self._lot      = lot
    self._remain   = 0

    self._buy_price  = None
    self._sell_price = None
    self._buy  = None
    self._sell = None

    self._spread_amount = spread_amount
    self._spread_band   = spread_band

    self._total = 0

    asyncio.create_task(self._main())

  async def _main(self):
    self._logger.info("started")
    while True:
      pair  = self._pair
      depth = pair.depth

      await depth.wait()
      if len(depth.bids) == 0 or len(depth.asks) == 0:
        continue

      # calculate best amount to buy/sell
      pos = self._remain / self._lot - 2
      buy_amount  = _quad( pos) * self._lot
      sell_amount = _quad(-pos) * self._lot

      # calculate ideal spread
      ask_delta_amount = self._spread_amount * (_quad( pos) + self._spread_band)
      bid_delta_amount = self._spread_amount * (_quad(-pos) + self._spread_band)

      # calculate best sell price
      if self._sell is not None and self._sell.done:
        ask_delta_amount += self._sell.amount
      ask  = 0
      prev = -1e100
      for i in range(len(depth.asks)):
        if (depth.asks[i][0]-prev) > pair.price_unit*1.5:
          ask = depth.asks[i][0] - pair.price_unit
        my_order =  \
          self._sell is not None and  \
          not self._sell.done and  \
          abs(self._sell_price-depth.asks[i][0]) < pair.price_unit
        if my_order:
          ask_delta_amount -= depth.asks[i][1] - self._sell.amount
          prev = -1e100
        else:
          ask_delta_amount -= depth.asks[i][1]
          prev = depth.asks[i][0]
        if ask_delta_amount < 0:
          break

      # calculate best buy price
      if self._buy is not None and self._buy.done:
        bid_delta_amount += self._buy.amount
      bid  = 0
      prev = 1e100
      for i in range(len(depth.bids)):
        if (prev-depth.bids[i][0]) > pair.price_unit*1.5:
          bid = depth.bids[i][0] + pair.price_unit
        my_order =  \
          self._buy is not None and  \
          not self._buy.done and  \
          abs(self._buy_price-depth.bids[i][0]) < pair.price_unit
        if my_order:
          bid_delta_amount -= depth.bids[i][1] - self._buy.amount
          prev = 1e100
        else:
          bid_delta_amount -= depth.bids[i][1]
          prev = depth.bids[i][0]
        if bid_delta_amount < 0:
          break

      if self._sell is not None:
        # check current SELL order
        await asyncio.sleep(0.5)
        await self._sell.update()

        if self._sell.done:
          amount = self._sell.amount - self._sell.remain
          self._remain -= amount
          if amount > 0:
            income = amount*self._sell.price
            self._total += income
            self._logger.info(f"<SELL> {amount} / {income} ({self._remain} / {self._total})")
          self._sell = None
        elif abs(self._sell_price-ask) >= pair.price_unit:
          await self._sell.cancel()
      elif sell_amount >= pair.order_unit:
        # order SELL
        self._sell_price = ask
        self._sell       = await self._order_sell(sell_amount)

      if self._buy is not None:
        # check current BUY order
        await self._buy.update()

        if self._buy.done:
          amount = self._buy.amount - self._buy.remain
          self._remain += amount
          if amount > 0:
            outgo = amount*self._buy.price
            self._total -= outgo
            self._logger.info(f"<BUY> {amount} / {outgo} ({self._remain} / {self._total})")
          self._buy = None
        elif abs(self._buy_price-bid) >= pair.price_unit:
          await self._buy.cancel()
      elif buy_amount >= pair.order_unit:
        # order BUY
        self._buy_price = bid
        self._buy       = await self._order_buy(buy_amount)

  async def _order_sell(self, amount):
     return await self._pair.sell_limit(amount, self._sell_price, True)
  async def _order_buy(self, amount):
    return await self._pair.buy_limit(amount, self._buy_price, True)


# https://kijitora-2018.hatenablog.com/entry/2018/12/23/102913
def _quad(x):
  if x < -1:
    return 1
  if x <= 1:
    return -1/4 * (x+1)**2 + 1
  return 0
